package com.yykj.hospital.server.service.account.impl;

import com.yykj.common.enums.ResultEnums;
import com.yykj.common.exception.NormalException;
import com.yykj.common.service.RedisService;
import com.yykj.common.util.PageRequestUtil;
import com.yykj.common.vo.PageData;
import com.yykj.hospital.server.dto.account.DoctorBalanceLogDTO;
import com.yykj.hospital.server.enums.IncomeExpendType;
import com.yykj.hospital.server.form.account.DoctorAccountSearchForm;
import com.yykj.hospital.server.model.entity.account.AccountDoctor;
import com.yykj.hospital.server.model.entity.account.AccountDoctorLog;
import com.yykj.hospital.server.model.entity.account.AccountLogIcon;
import com.yykj.hospital.server.model.entity.bank.DoctorBankCashApply;
import com.yykj.hospital.server.model.repository.account.AccountDoctorLogRepository;
import com.yykj.hospital.server.model.repository.account.AccountDoctorRepository;
import com.yykj.hospital.server.model.repository.bank.DoctorBankCashApplyRepository;
import com.yykj.hospital.server.service.account.AccountLogIconService;
import com.yykj.hospital.server.service.account.DoctorAccountService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
@Slf4j
public class DoctorAccountServiceImpl implements DoctorAccountService {
    @Override
    public AccountDoctorLog income(AccountDoctorLog accountDoctorLog) {
        String key = "account_lock_" + accountDoctorLog.getDoctorId();
        String value = redisService.getKeyLock(key);
        AccountDoctor accountDoctor = getAccountDoctor(accountDoctorLog.getHospitalId(), accountDoctorLog.getDoctorId());
        accountDoctor.setBalance(accountDoctor.getBalance().add(accountDoctorLog.getPrice()));
        accountDoctorRepository.save(accountDoctor);
        accountDoctorLog.setBalance(accountDoctor.getBalance());
        accountDoctorLog = accountDoctorLogRepository.save(accountDoctorLog);
        redisService.unlock(key, value);
        return accountDoctorLog;

    }


    @Override
    public AccountDoctorLog expend(AccountDoctorLog accountDoctorLog) {

        String key = "account_lock_" + accountDoctorLog.getDoctorId();
        String value = redisService.getKeyLock(key);
        AccountDoctor accountDoctor = getAccountDoctor(accountDoctorLog.getHospitalId(), accountDoctorLog.getDoctorId());

        if (accountDoctor.getBalance().compareTo(accountDoctorLog.getPrice()) < 0) {
            throw new NormalException(ResultEnums.NOT_ENOUGH_BALANCE);
        }
        accountDoctor.setBalance(accountDoctor.getBalance().subtract(accountDoctorLog.getPrice()));
        accountDoctorRepository.save(accountDoctor);

        accountDoctorLog.setBalance(accountDoctor.getBalance());
        accountDoctorLog = accountDoctorLogRepository.save(accountDoctorLog);
        redisService.unlock(key, value);
        return accountDoctorLog;
    }

    @Override
    public PageData<AccountDoctorLog> getDoctorAccountLog(DoctorAccountSearchForm form) {
        PageRequest pageRequest = PageRequest.of(PageRequestUtil.getPage(form.getCurrentPage()), PageRequestUtil.getSize(form.getPageSize()), Sort.by(Sort.Order.desc("addTime")));
        Page<AccountDoctorLog> all = accountDoctorLogRepository.findAll(generateSpecification(form), pageRequest);
        return PageData.formatList(all, PageRequestUtil.getPage(form.getCurrentPage()), PageRequestUtil.getSize(form.getPageSize()));
    }

    @Override
    public AccountDoctor getAccountDoctor(Integer hospitalId, String doctorId) {
        AccountDoctor accountDoctor = accountDoctorRepository.findFirstByDoctorId(doctorId);
        if (accountDoctor == null) {
            accountDoctor = new AccountDoctor();
            accountDoctor.setHospitalId(hospitalId);
            accountDoctor.setDoctorId(doctorId);
            accountDoctor.setBalance(BigDecimal.ZERO);
        }
        return accountDoctor;
    }

    @Override
    public PageData<DoctorBalanceLogDTO> listDoctorBalanceLog(DoctorAccountSearchForm form) {
        PageRequest pageRequest = PageRequest.of(PageRequestUtil.getPage(form.getCurrentPage()), PageRequestUtil.getSize(form.getPageSize()), Sort.by(Sort.Order.desc("addTime")));
        Page<AccountDoctorLog> all = accountDoctorLogRepository.findAll(generateSpecification(form), pageRequest);
        PageData<AccountDoctorLog> pageData = PageData.formatList(all, form.getCurrentPage(), form.getPageSize());
        PageData<DoctorBalanceLogDTO> ret = new PageData<>();
        ret.setCurrentPage(pageData.getCurrentPage());
        ret.setPageSize(pageData.getPageSize());
        ret.setTotalPage(pageData.getTotalPage());
        ret.setTotalCount(pageData.getTotalCount());
        List<DoctorBalanceLogDTO> list = new ArrayList<>();
        Map<String, AccountLogIcon> iconMap = iconService.listAccountLogIcon();

        List<String> orderIds = new ArrayList<>();
        Map<String, DoctorBankCashApply> applyMap = new HashMap<>();
        if (IncomeExpendType.expend.equals(form.getIncomeExpend())) {
            for (AccountDoctorLog accountDoctorLog : all) {
                orderIds.add(accountDoctorLog.getOrderNum());
            }
            List<DoctorBankCashApply>  applyList = applyRepository.findAllByOrderNoIn(orderIds);
            for (DoctorBankCashApply apply : applyList) {
                applyMap.put(apply.getOrderNo(), apply);
            }
        }


        for (AccountDoctorLog log : pageData.getList()) {
            DoctorBalanceLogDTO dto = new DoctorBalanceLogDTO();
            BeanUtils.copyProperties(log, dto);
            AccountLogIcon icon = iconMap.get(log.getLogType().name());
            if (icon != null) {
                dto.setLogTypeName(icon.getName());
                dto.setLogTypeIcon(icon.getIcon());
            }
            DoctorBankCashApply apply = applyMap.get(log.getOrderNum());
            if(apply !=null){
                dto.setFee(apply.getApplyFee());
                dto.setAmount(apply.getAmount());
            }
            list.add(dto);
        }
        ret.setList(list);
        return ret;
    }

    private Specification<AccountDoctorLog> generateSpecification(DoctorAccountSearchForm form) {
        return new Specification<AccountDoctorLog>() {
            @Override
            public Predicate toPredicate(Root<AccountDoctorLog> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
                List<Predicate> predicateList = new ArrayList<>();
                predicateList.add(builder.equal(root.get("doctorId"), form.getDoctorId()));
                if (form.getStartDate() != null) {
                    LocalDateTime startTime = LocalDateTime.of(form.getStartDate(), LocalTime.MIN);
                    predicateList.add(builder.greaterThanOrEqualTo(root.get("addTime"), startTime));
                }
                if (form.getEndDate() != null) {
                    LocalDateTime endTime = LocalDateTime.of(form.getStartDate(), LocalTime.MAX);
                    predicateList.add(builder.lessThanOrEqualTo(root.get("addTime"), endTime));
                }

                if (form.getIncomeExpend() != null) {
                    predicateList.add(builder.equal(root.get("incomeExpend"), form.getIncomeExpend()));
                }

                Predicate[] predicates = new Predicate[predicateList.size()];
                return query.where(predicateList.toArray(predicates)).getRestriction();
            }
        };
    }

    private AccountDoctorLogRepository accountDoctorLogRepository;
    private AccountDoctorRepository accountDoctorRepository;
    private AccountLogIconService iconService;
    private RedisService redisService;

    private DoctorBankCashApplyRepository applyRepository;

    @Autowired
    public void setDoctorAccountLogRepository(AccountDoctorLogRepository accountDoctorLogRepository) {
        this.accountDoctorLogRepository = accountDoctorLogRepository;
    }

    @Autowired
    public void setDoctorAccountRepository(AccountDoctorRepository accountDoctorRepository) {
        this.accountDoctorRepository = accountDoctorRepository;
    }

    @Autowired
    public void setIconService(AccountLogIconService iconService) {
        this.iconService = iconService;
    }

    @Autowired
    public void setRedisService(RedisService redisService) {
        this.redisService = redisService;
    }

    @Autowired
    public void setApplyRepository(DoctorBankCashApplyRepository applyRepository) {
        this.applyRepository = applyRepository;
    }
}
